package net
{
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.Socket;
	import flash.utils.ByteArray;
	
	[Event(name="connect",type="flash.events.Event")]
	[Event(name="close",type="flash.events.Event")]
	[Event(name="error",type="flash.events.ErrorEvent")]
	[Event(name="data",type="net.SocketEvent")]
	public class ByteSocket extends EventDispatcher
	{
		private var _debug:Boolean;
		
		private var _socket :Socket;
		
		private var _ip:String;
		
		private var _port:Number;
		
		private var _send_fsm:FSM;
		
		private var _receive_fsm:FSM;
		
		private var _encrypted:Boolean;
		
		private static var KEY:Array = [0xae, 0xbf, 0x56, 0x78, 0xab, 0xcd, 0xef, 0xf1];
		public static var RECEIVE_KEY:ByteArray;
		public static var SEND_KEY:ByteArray;
		
		public function ByteSocket(encrypted:Boolean = true,debug:Boolean = false) 
		{
			_readBuffer = new ByteArray();
			_send_fsm = new FSM(0x7abcdef7,1501);
			_receive_fsm = new FSM(0x7abcdef7,1501);
			_headerTemp = new ByteArray();
			_encrypted = encrypted;
			_debug = debug;
			setKey(KEY);
		}
		
		public function setKey(key:Array):void
		{
			RECEIVE_KEY = new ByteArray();
			SEND_KEY = new ByteArray();
			for(var i:int=0;i<8;i++)
			{
				RECEIVE_KEY.writeByte(key[i]);
				SEND_KEY.writeByte(key[i]);
			}
		}
		
		public function resetKey():void
		{
			setKey(KEY);
		}
		
		public function setFsm(adder:int,muliter:int):void
		{
			_send_fsm.setup(adder,muliter);
			_receive_fsm.setup(adder,muliter);
		}
		
		public function connect(ip:String,port:Number) : void 
		{
			try
			{
				if(_socket)
				{
					close();
				}
				
				_socket = new Socket();
				addEvent(_socket);
				_ip = ip;
				_port = port;
				_readBuffer.position = 0;
				_readOffset = 0;
				_writeOffset = 0;
				_socket.connect(ip,port);
			}
			catch(err:Error)
			{
				dispatchEvent(new ErrorEvent(ErrorEvent.ERROR,false,false,err.message));
			}
		}
		
		private function addEvent(socket:Socket):void
		{
			socket.addEventListener(Event.CONNECT, this.handleConnect);
			socket.addEventListener(Event.CLOSE, this.handleClose);
			socket.addEventListener(ProgressEvent.SOCKET_DATA, handleIncoming);
			socket.addEventListener(IOErrorEvent.IO_ERROR, handleIoError);
			socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR,handleIoError);
		}
		
		private function removeEvent(socket:Socket):void
		{
			socket.removeEventListener(Event.CONNECT, this.handleConnect);
			socket.removeEventListener(Event.CLOSE, this.handleClose);
			socket.removeEventListener(ProgressEvent.SOCKET_DATA, handleIncoming);
			socket.removeEventListener(IOErrorEvent.IO_ERROR, handleIoError);
			socket.removeEventListener(SecurityErrorEvent.SECURITY_ERROR,handleIoError);
		}
		
		public function get connected():Boolean
		{
			return _socket && _socket.connected;
		}

		public function isSame(ip : String,port : int) : Boolean 
		{
			return _ip == ip && port == _port;
		}

		public function send(pkg :PackageOut) : void 
		{
			if(_socket && _socket.connected)
			{
				pkg.pack();
				if(_debug)
				{
//					trace(ByteUtils.ToHexDump("Send:" + _port,pkg,0,pkg.length));
				}
				if(_encrypted)
				{
//					traceArr(SEND_KEY);
//					tracePkg(pkg,"加密前： ",4);
					for(var i:int = 0;i<pkg.length;i++)
					{
						if(i>0)
						{
							SEND_KEY[i % 8] = (SEND_KEY[i % 8] + pkg[i-1]) ^ i;
							pkg[i] = (pkg[i] ^ SEND_KEY[i % 8]) + pkg[i-1];
						}else
						{
							pkg[0] = pkg[0] ^ SEND_KEY[0];
						}
					}
//					tracePkg(pkg,"加密后： ",4);
//					for(var i:int = 0; i < pkg.length; i ++)
//					{
//						_socket.writeByte(pkg[i] ^ ((key++ & (0xff << 16)) >> 16));
//					}
//					_send_fsm.updateState();
				}
//				else
//				{
					_socket.writeBytes(pkg,0,pkg.length);
//				}
				_socket.flush();
			}
		}
		
		public function sendString(data:String):void
		{
			if(_socket.connected)
			{
				_socket.writeUTF(data);
				_socket.flush();
			}
		}
		
		public  function close() : void 
		{
			removeEvent(_socket);
			if(_socket.connected)
			{
				_socket.close();
			}
		}

		private function handleConnect(event : Event) : void 
		{
			try
			{
				_send_fsm.reset();
				_receive_fsm.reset();
				_send_fsm.setup(0x7abcdef7,1501);
				_receive_fsm.setup(0x7abcdef7,1501);
				dispatchEvent(new Event(Event.CONNECT));
			}
			catch(e:Error)
			{
//				trace(e.getStackTrace());
			}
		}

		private function handleClose(event : Event) : void 
		{
			try
			{
				removeEvent(_socket);
				dispatchEvent(new Event(Event.CLOSE));
			}
			catch(e:Error)
			{
//				trace(e.getStackTrace());
			}
		}
		
		private function handleIoError(event :ErrorEvent) : void 
		{
			try
			{
				dispatchEvent(new ErrorEvent(ErrorEvent.ERROR,false,false,event.text));
			}
			catch(e:Error)
			{
//				trace(e.getStackTrace());
			}
		}
		
		private function handleIncoming(event :ProgressEvent) : void 
		{
			if(_socket.bytesAvailable > 0)
			{
				var len:int = _socket.bytesAvailable;
				_socket.readBytes(_readBuffer,_writeOffset,_socket.bytesAvailable);
				//trace("Receive:" + _port,len," bytes");
				_writeOffset += len;
				if(_writeOffset > 1)
				{
					_readBuffer.position = 0;
					_readOffset = 0;
					if(_readBuffer.bytesAvailable >= PackageIn.HEADER_SIZE)
					{
						readPackage();
					}
				}
			}
		}
		
		private var _readBuffer:ByteArray;
		private var _readOffset:int;
		private var _writeOffset:int;
		private var _headerTemp:ByteArray;
		private function readPackage():void
		{
			var dataLeft:int = _writeOffset - _readOffset;
			do
			{
				var len:int = 0;
				while(_readOffset + 6 < _writeOffset)
				{
					_headerTemp.position = 0;
					_headerTemp.writeByte(_readBuffer[_readOffset]);
					_headerTemp.writeByte(_readBuffer[_readOffset+1]);
					_headerTemp.writeByte(_readBuffer[_readOffset+2]);
					_headerTemp.writeByte(_readBuffer[_readOffset+3]);
					_headerTemp.writeByte(_readBuffer[_readOffset+4]);
					_headerTemp.writeByte(_readBuffer[_readOffset+5]);
					
					_headerTemp.position = 0;
					if(_headerTemp.readShort() == PackageOut.HEADER)
					{
						len = _headerTemp.readInt();
						break;
					}else
					{
						_readOffset ++;
					}
				}
				
				dataLeft = _writeOffset - _readOffset;
				if(dataLeft >= len && len != 0)
				{
					_readBuffer.position = _readOffset;
					var buff:PackageIn = new PackageIn();
					buff.load(_readBuffer,len);
					_readOffset += len;
					dataLeft = _writeOffset - _readOffset;
					handlePackage(buff);
				}else
				{
					break;
				}
			}
			while(dataLeft >= PackageIn.HEADER_SIZE);	
			
			_readBuffer.position = 0;
			if(dataLeft > 0)
			{
				_readBuffer.writeBytes(_readBuffer,_readOffset,dataLeft);
			}
			_readOffset = 0;
			_writeOffset = dataLeft;
		}
		
		private function copyByteArray(src:ByteArray):ByteArray
		{
			var result:ByteArray = new ByteArray();
			for(var i:int=0;i<src.length;i++)
			{
				result.writeByte(src[i]);
			}
			return result;
		}
		
		public function decrptBytes(src:ByteArray,len:int,key:ByteArray):ByteArray
		{
//			traceArr(RECEIVE_KEY);
//			tracePkg(src,"解密前： ");
			var i:int=0;
			var result:ByteArray = new ByteArray();
			for(i=0;i<len;i++)
			{
				result.writeByte(src[i]);
			}
			for(i=0;i<len;i++)
			{
				if(i>0)
				{
					key[i % 8] = (key[i % 8] + src[i-1]) ^ i;
					result[i] = (src[i] - src[i-1]) ^ key[i % 8];
				}else
				{
					result[0] = src[0] ^ key[0];
				}
			}
//			tracePkg(result,"解密后： ");
			return result;
		}
		
		private function tracePkg(src:ByteArray,des:String,len:int=-1):void
		{
			var str:String=des;
			var l:int = len<0?src.length:len;
			for(var i:int=0;i<l;i++)
			{
				str+=String(src[i])+", ";
			}
			trace(str);
		}
		
		private function traceArr(arr:ByteArray):void
		{
			var str:String = "[";
			for(var i:int=0;i<arr.length;i++)
			{
				str+=(arr[i]+" ");
			}
			str+="]";
//			trace(str);
		}
		
		private var pkgNumber:int=0;
		private function handlePackage(pkg:PackageIn):void
		{
			//trace("recieved pkg: ",pkgNumber++);
			if(_debug)
			{
			//	trace(ByteUtils.ToHexDump("Receive Pkg:" + _port,pkg,0,pkg.length));
			}
			try
			{
				//trace(pkg.checkSum,pkg.calculateCheckSum());
					pkg.position = PackageIn.HEADER_SIZE;
					dispatchEvent(new SocketEvent(SocketEvent.DATA,pkg));
			}
			catch(err:Error)
			{
//				trace("handlePackage:",err.getStackTrace());
//				trace(ByteUtils.ToHexDump("Bytes Left:",_readBuffer,_readOffset,_readBuffer.bytesAvailable));
			}
		}
		
		public function dispose():void
		{
			if(_socket.connected)
				_socket.close();
			_socket = null;
		}
	}
}

class FSM
{
	private var _state:int;
	private var _adder:int;
	private var _multiper:int;
	
	public function getState():int
	{
		return _state;
	}
	
	public function FSM(adder:int,multiper:int)
	{
		setup(adder,multiper);
	}
	
	public function reset():void
	{
		_state = 0;
	}
	
	public function setup(adder:int,multiper:int):void
	{
		_adder = adder;
		_multiper = multiper;
		updateState();
	}
	
	public function updateState():int
	{
		 _state = ((~ _state) + _adder) * _multiper;
   		 _state = _state ^ (_state >> 16);
		return _state;
	}
}